#include <cassert>
#include <cstring>
#include <iostream>
#include <string>

#include "charconv.hpp"

#if defined(__has_include) && __has_include(<charconv>)
# include <charconv>
#endif

#if CHARCONV_CPLUSPLUS >= 201703L && defined(__cpp_lib_to_chars)
# define HAS_STD_CHARCONV
#endif

#define ASSERT_STREQ(str1, str2) \
    if (std::string(str1) != std::string(str2)) { \
        std::cout << "\033[31;1m [ASSERT Failed] \033[0m " << __FILE__ << ":" << __LINE__ \
            << ": ASSERT_STREQ failed, str1=" << (str1) << ", str2=" << (str2) << std::endl; \
        assert(false); \
    }

#define ASSERT_EQ(value1, value2) \
    if ((value1) != (value2)) { \
        std::cout << "\033[31;1m [ASSERT Failed] \033[0m " << __FILE__ << ":" << __LINE__ \
            << ": ASSERT_STREQ failed, value1=" << (value1) << ", value2=" << (value2)) << std::endl; \
        assert(false); \
    }

#define ASSERT_TRUE(cond) \
    if (!(cond)) { \
        std::cout << "\033[31;1m [ASSERT Failed] \033[0m " << __FILE__ << ":" << __LINE__ \
            << ": ASSERT_TRUE failed!" << std::endl; \
        assert(false); \
    }

#define ASSERT_FALSE(cond) \
    if (!(cond)) { \
        std::cout << "\033[31;1m [ASSERT Failed] \033[0m " << __FILE__ << ":" << __LINE__ \
            << ": ASSERT_FALSE failed!" << std::endl; \
        assert(false); \
    }

#define RESET_BUFF(buff, len) std::memset(buff, 0, len)

void test_101();
void test_1001();

template<typename T>
void test_with_stdcharconv(T value)
{
#ifdef HAS_STD_CHARCONV
    std::cout << "Starting test_with_stdcharconv(" << value << ")" << std::endl;
    constexpr int len = 1000;
    char buff1[len];
    char buff2[len];

    RESET_BUFF(buff1, len);
    RESET_BUFF(buff2, len);
    to_chars(buff1, buff1 + len, value, 2);
    std::to_chars(buff2, buff2 + len, value, 2);
    ASSERT_STREQ(buff1, buff2);

    RESET_BUFF(buff1, len);
    RESET_BUFF(buff2, len);
    to_chars(buff1, buff1 + len, value, 5);
    std::to_chars(buff2, buff2 + len, value, 5);
    ASSERT_STREQ(buff1, buff2);

    RESET_BUFF(buff1, len);
    RESET_BUFF(buff2, len);
    to_chars(buff1, buff1 + len, value, 8);
    std::to_chars(buff2, buff2 + len, value, 8);
    ASSERT_STREQ(buff1, buff2);

    RESET_BUFF(buff1, len);
    RESET_BUFF(buff2, len);
    to_chars(buff1, buff1 + len, value);
    std::to_chars(buff2, buff2 + len, value);
    ASSERT_STREQ(buff1, buff2);

    RESET_BUFF(buff1, len);
    RESET_BUFF(buff2, len);
    to_chars(buff1, buff1 + len, value, 16);
    std::to_chars(buff2, buff2 + len, value, 16);
    ASSERT_STREQ(buff1, buff2);

    std::cout << "test_with_stdcharconv() finished!" << std::endl;
#endif
}

int main(int argc, char* argv[])
{
    std::cout << "********* __cplusplus = " << CHARCONV_CPLUSPLUS << std::endl;

    test_101();
    test_1001();
    
    test_with_stdcharconv(-1);
    test_with_stdcharconv(0);
    test_with_stdcharconv(99999);
    test_with_stdcharconv(999999999999999);
    test_with_stdcharconv(-99999);
    test_with_stdcharconv(unsigned(513));

    return 0;
}

void test_101()
{
    constexpr int len = 1000;
    char buff[len] = {0};
    char* first = buff;
    char* last = buff + len;
    to_chars_result res;

    char v = 101;
    std::cout << "Convert " << v << " to string: " << std::endl;

    RESET_BUFF(buff, len);
    res = to_chars(first, last, v);
    ASSERT_TRUE(res.ec != std::errc::value_too_large);
    ASSERT_STREQ(buff, "101");
    std::cout << "  base=10: "<< first << std::endl;
    
    RESET_BUFF(buff, len);
    res = to_chars(first, last, v, 3);
    ASSERT_TRUE(res.ec != std::errc::value_too_large);
    ASSERT_STREQ(buff, "10202");
    std::cout << "  base=3: " << first << std::endl;

    RESET_BUFF(buff, len);
    res = to_chars(first, last, v, 5);
    ASSERT_TRUE(res.ec != std::errc::value_too_large);
    ASSERT_STREQ(buff, "401");
    std::cout << "  base=5: " << first << std::endl;

    RESET_BUFF(buff, len);
    res = to_chars(first, last, v, 16);
    ASSERT_TRUE(res.ec != std::errc::value_too_large);
    ASSERT_STREQ(buff, "65");
    std::cout << "  base=16: " << first << std::endl;

    RESET_BUFF(buff, len);
    res = to_chars(first, last, v, 8);
    ASSERT_TRUE(res.ec != std::errc::value_too_large);
    ASSERT_STREQ(buff, "145");
    std::cout << "  base=8: " << first << std::endl;

    RESET_BUFF(buff, len);
    res = to_chars(first, last, v, 2);
    ASSERT_TRUE(res.ec != std::errc::value_too_large);
    ASSERT_STREQ(buff, "1100101");
    std::cout << "  base=2: " << first << std::endl;
}

void test_1001()
{
    constexpr int len = 1000;
    char buff[len] = {0};
    char* first = buff;
    char* last = buff + len;
    to_chars_result res;

    unsigned long long v = 1001;
    std::cout << "Convert " << v << " to string: " << std::endl;

    RESET_BUFF(buff, len);
    res = to_chars(first, last, v);
    ASSERT_TRUE(res.ec != std::errc::value_too_large);
    ASSERT_STREQ(buff, "1001");
    std::cout << "  base=10: "<< first << std::endl;
    
    RESET_BUFF(buff, len);
    res = to_chars(first, last, v, 3);
    ASSERT_STREQ(buff, "1101002");
    std::cout << "  base=3: " << first << std::endl;

    RESET_BUFF(buff, len);
    res = to_chars(first, last, v, 5);
    ASSERT_TRUE(res.ec != std::errc::value_too_large);
    ASSERT_STREQ(buff, "13001");
    std::cout << "  base=5: " << first << std::endl;

    RESET_BUFF(buff, len);
    res = to_chars(first, last, v, 16);
    ASSERT_TRUE(res.ec != std::errc::value_too_large);
    ASSERT_STREQ(buff, "3e9");
    std::cout << "  base=16: " << first << std::endl;

    RESET_BUFF(buff, len);
    res = to_chars(first, last, v, 8);
    ASSERT_TRUE(res.ec != std::errc::value_too_large);
    ASSERT_STREQ(buff, "1751");
    std::cout << "  base=8: " << first << std::endl;

    RESET_BUFF(buff, len);
    res = to_chars(first, last, v, 2);
    ASSERT_TRUE(res.ec != std::errc::value_too_large);
    ASSERT_STREQ(buff, "1111101001");
    std::cout << "  base=2: " << first << std::endl;
}